import axios, { AxiosRequestConfig } from 'axios'
import { Workbook, type Cell } from 'exceljs'
import FileSaver from 'file-saver' //导出文件所需包
import { cloneDeep, isEmpty } from 'lodash-es'
import { decimalRound } from './mixins'

// /**
//  * 字符串转2进制数组
//  */
// function s2ab(s: string) {
//     const buf = new ArrayBuffer(s.length)
//     const view = new Uint8Array(buf)
//     for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
//     return buf
// }

/**
 * 处理多层级字段取值
 * 需要处理的多层级数据重新组装放到最外层
 */
function calcFields(data: any, fields: string[]) {
  data.forEach((element: any) => {
    let tempData = cloneDeep(element)
    fields.forEach((item) => {
      tempData = tempData[item]
    })
    element[fields.join('')] = fields.length > 0 ? tempData : ''
  })
}

// 其它参数
interface imageParams {
  field: string //图片字段
  colNum: number //第几列  初始从0开始
}
export interface ExcelParams {
  sheetNames?: string[] //工作表名称-不传默认Sheet1
  merges?: string[] | string[][] //需要合并的单元格
  required?: string[] //表格必填项
  requiredDirection?: 'left' | 'right' //必填红星方向
  isSubtitle?: boolean //是否有副标题
  images?: imageParams[] | imageParams[][] //兼容多列/多表图片导出
  isDownload?: boolean // 是否立即下载，默认true
  rowNumFmt?: []
}
// 表头
export interface ExcelHeader {
  title: string //表头
  value: string //表格数据中对应的key
  width?: number //表头宽度
  tiptext?: string //表头批注
  formulae?: string[] //下拉列表数据
  type?: string // 数据格式
  formulaCus?: (worksheet: any) => void //自定义公式
}
/**
 * 导出excel表格数据
 * @param excelTableData 表格数据
 * @param excelHeaderData 表头数据{title:'中文表头',value:'表格数据中对应的key',width:'表头宽度'，默认120px}
 * @param fileName 文件名称
 * @param params 配置参数，详情见ExcelParams
 */
export function exportExcel(
  excelTableData: any[] | any[][],
  excelHeaderData: ExcelHeader[] | ExcelHeader[][],
  fileName: string,
  params: ExcelParams = {}
) {
  return new Promise((resolve) => {
    const exportParam: ExcelParams = { isDownload: true, ...params }
    const workbook = new Workbook()
    let sheetNames = ['Sheet1']
    if (exportParam.sheetNames) {
      sheetNames = exportParam.sheetNames
    }
    let sheetNum = 0
    sheetNames.forEach((sheetItem, index) => {
      const worksheet = workbook.addWorksheet(sheetItem)
      // 获取到工作表的数据
      const tableData = sheetNames.length > 1 ? excelTableData[index] : excelTableData
      // 获取到工作表头的数据
      const headerData = (
        sheetNames.length > 1 ? cloneDeep(excelHeaderData[index]) : excelHeaderData
      ) as ExcelHeader[]
      let imgSetData: any = []
      if (exportParam.images && !isEmpty(exportParam.images)) {
        // 获取到图片设置参数
        imgSetData =
          sheetNames.length > 1 && exportParam.images.length > 1
            ? exportParam.images[index]
            : exportParam.images
      }

      //生成列
      worksheet.columns = headerData.map((item: ExcelHeader) => {
        //字段为空时给个默认key
        if (!item.value) {
          item.value = '无'
        }
        if (item.value.includes('.')) {
          calcFields(tableData, item.value.split('.'))
          item.value = item.value.split('.').join('')
        }
        const col: any = {
          header: item.title,
          key: item.value,
          width: item.width ? item.width / 8 : 14
        }
        if (item.type) {
          col.style = {
            numFmt: item.type
          }
        }
        return col
      })
      worksheet.addRows(tableData)
      //遍历所有数据
      worksheet.eachRow({ includeEmpty: true }, function (row, rowNumber) {
        //判断有无副标题设置高度
        if (rowNumber == 2 && exportParam.isSubtitle) {
          row.height = 30
        } else {
          row.height = 50
        }
        row.eachCell({ includeEmpty: true }, (cell: Cell, colNumber) => {
          let font: any = { size: 12 }
          cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true } // 设置对齐
          if (cell.text.includes('http')) {
            // 设置链接颜色
            font.color = { argb: '0000ff' }
          }
          if (rowNumber == 1) {
            // 设置表头的样式
            font = {
              size: 14
            }
            if (
              !isEmpty(exportParam?.required) &&
              exportParam.required?.includes(cell.value as string)
            ) {
              font.color = { argb: 'ff0000' }
              if (exportParam.requiredDirection == 'right') cell.value = cell.value + '*'
              else cell.value = '*' + cell.value
            }
            // 默认表头黄色
            cell.fill = {
              type: 'pattern',
              pattern: 'solid',
              fgColor: { argb: 'ffff00' }
            }
          } else if (imgSetData && !isEmpty(imgSetData)) {
            //图片对应的链接文本替换成图片之前先清空单元格的值
            imgSetData.forEach((img: any) => {
              if (img.colNum == colNumber - 1) {
                if (exportParam.isSubtitle && rowNumber == 2) {
                  return
                }
                cell.value = ''
              }
            })
          }
          //格式化表格日期-部分电脑的xlsx版本太低不会自动识别
          if (
            isNaN(cell.value as any) &&
            !isNaN(Date.parse(cell.value + '')) &&
            parseInt(cell.value + '') &&
            (parseInt(cell.value + '') + '').length == 4 &&
            (cell.value + '').indexOf('%') == -1
          ) {
            cell.value = new Date(cell.value + '')
          }
          cell.font = font
          cell.border = {
            top: { style: 'thin' },
            left: { style: 'thin' },
            bottom: { style: 'thin' },
            right: { style: 'thin' }
          }
        })
      })
      //合并单元格
      if (exportParam && exportParam?.merges && exportParam.merges.length > 0) {
        if (exportParam.merges[0] instanceof Array) {
          const merges = exportParam.merges[index] as string[]
          merges.forEach((item: any) => {
            worksheet.mergeCells(item)
          })
        } else {
          exportParam.merges.forEach((item: any) => {
            worksheet.mergeCells(item)
          })
        }
      }

      // 遍历列，针对列进行操作，如设置样式，增加提示等
      for (const iterator of headerData) {
        const dobCol = worksheet.getColumn(iterator.value) // 获取列的对象

        if (iterator.formulaCus) {
          iterator.formulaCus(worksheet)
        }

        if (iterator.tiptext) {
          // ts类型里没有dataValidations，但是包里面实际是有这个方法
          worksheet.dataValidations.add(`${dobCol.letter}2:${dobCol.letter}9999`, {
            type: 'textLength',
            formulae: [],
            showInputMessage: true,
            promptTitle: '提示',
            prompt: iterator.tiptext
          })
        }
      }

      //遍历列，针对列进行操作，如设置样式，增加下拉默认数据
      for (const iterator of headerData) {
        const dobCol = worksheet.getColumn(iterator.value) // 获取列的对象
        if (!isEmpty(iterator.formulae)) {
          // ts类型里没有dataValidations，但是包里面实际是有这个方法
          worksheet.dataValidations.add(`${dobCol.letter}2:${dobCol.letter}9999`, {
            type: 'list',
            formulae: iterator.formulae as any[]
          })
        }
      }

      //获取需要设置的图片数据
      // 如果有图片则进入图片处理，没有则直接输出文件
      if (imgSetData && imgSetData.length > 0) {
        const imageReqData = assembleImageReqData(imgSetData, tableData)
        const imageData = imageReqData.map((iterator) => {
          return getImage(iterator.apis).then((res) => {
            for (let index = 0; index < res.length; index++) {
              const imgData = res[index]
              if (!imgData) continue
              // 图片横向摆放，当有多张图时，计算图片的X轴位置
              const imageLoction = decimalRound(
                iterator.colNum +
                  iterator.otherParam[index].num / iterator.otherParam[index].maxNum -
                  1 / iterator.otherParam[index].maxNum,
                2
              )
              // 创建图元
              const imageId = workbook.addImage({ buffer: imgData, extension: 'png' })
              // 将图片插入Excel
              worksheet.addImage(imageId, {
                tl: {
                  col: Number(imageLoction),
                  row: iterator.otherParam[index].row + 1
                },
                ext: { width: 100, height: 50 }
                //如需链接 可使用以下案例
                // hyperlinks: {
                //     hyperlink: 'http://www.somewhere.com',
                //     tooltip: 'http://www.somewhere.com'
                //   }
              })
            }
          })
        })
        // todo 抽象成函数
        Promise.all(imageData)
          .then(() => {
            sheetNum++
            if (sheetNum == sheetNames.length) {
              workbook.xlsx.writeBuffer().then((buffer) => {
                //这里为type
                const blob = new Blob([buffer], {
                  type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'
                })
                if (exportParam.isDownload) {
                  FileSaver(blob, `${fileName}.xlsx`)
                }
                resolve(blob)
              })
            }
          })
          .catch((reason) => {
            console.log('下载图片错误', reason)
          })
      } else {
        sheetNum++
        if (sheetNum == sheetNames.length) {
          workbook.xlsx.writeBuffer().then((buffer) => {
            //这里为type
            const blob = new Blob([buffer], {
              type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'
            })
            if (exportParam.isDownload) {
              FileSaver(blob, `${fileName}.xlsx`)
            }
            resolve(blob)
          })
        }
      }
    })
  })
}

/**
 * 把文件按照二进制进行读取
 * @param file
 * @returns
 */
export const readFile = (file: File) => {
  return new Promise((resolve) => {
    const reader = new FileReader()
    reader.readAsBinaryString(file)
    reader.onload = (ev) => {
      resolve(ev.target?.result)
    }
  })
}
/**
 * 根据sheet索引读取Excel文件中的数据
 * @param file 文件
 * @param sheetIndex excel文件中sheet表索引 默认是从0开始
 * @returns  将表中的数据以json形式的数据返回
 */
export const readExcelFile = async (file: any, sheetIndex: number) => {
  const datas: any = []
  const header: any = []
  const workbook = new Workbook()
  const data = await workbook.xlsx.load(file)
  data.worksheets[sheetIndex].eachRow({ includeEmpty: true }, function (row, rowNumber) {
    const rowObj: any = {}
    row.eachCell({ includeEmpty: true }, function (cell: any, colNumber) {
      if (rowNumber == 2 && cell.text) {
        header.push(header.includes(cell.text) ? cell.text + 'colNumber' : cell.text)
      } else if (rowNumber > 2 && header[colNumber - 1] && cell.text) {
        if (!isEmpty(cell.text.richText)) {
          rowObj[header[colNumber - 1]] = cell.text.richText[0].text
        } else rowObj[header[colNumber - 1]] = cell.text
      }
    })
    if (rowNumber > 2 && !isEmpty(rowObj)) {
      datas.push(rowObj)
    }
  })
  return datas
}

interface imgReqItem {
  colNum: number
  apis: AxiosRequestConfig[]
  otherParam: { row: number; num: number; maxNum: number }[]
}
/**
 * @description: 组装图片请求数据
 * @param {imageParams} images 导出文件时传入的图片参数
 * @param {any} tableData  导出文件时传入的数据
 * @return {*}
 */
export const assembleImageReqData = (images: imageParams[], tableData: any): imgReqItem[] => {
  const imageReqData: imgReqItem[] = []
  images.forEach((img: imageParams) => {
    const imgObj: imgReqItem = {
      colNum: img.colNum,
      apis: [],
      otherParam: []
    }
    for (let index = 0; index < tableData.length; index++) {
      const item = tableData[index]
      if (item[img.field] instanceof Array) {
        item[img.field].forEach((imgItem: string, imgIndex: number) => {
          imgObj.apis.push({
            method: 'get',
            url: imgItem,
            responseType: 'blob'
          })
          imgObj.otherParam.push({ row: index, num: imgIndex + 1, maxNum: item[img.field].length })
        })
      } else {
        imgObj.apis.push({
          method: 'get',
          url: item[img.field],
          responseType: 'blob'
        })
        imgObj.otherParam.push({ row: index, num: 1, maxNum: 1 })
      }
    }
    imageReqData.push(imgObj)
  })
  return imageReqData
}

/**
 * @description: 请求接口获取图片buffer
 * @param {AxiosRequestConfig} reqConfigData 通过assembleImageReqData处理后的数据
 * @param {number} reqNum
 * @return {*}
 */
export const getImage = (reqConfigData: AxiosRequestConfig[], reqNum = 100): Promise<any[]> => {
  let imagePage = 0 // 记录图片请求到了那一页
  const images: any[] = [] // 得到的图片
  return new Promise((resolve, reject) => {
    let tempTime: NodeJS.Timeout
    const reqImg = () => {
      const imageBlock = reqConfigData
        .slice(imagePage * reqNum, (imagePage + 1) * reqNum)
        .map((item) => axios(item))
      Promise.allSettled(imageBlock)
        .then((res) => {
          res.reduce((pre, next) => {
            if (next.status === 'fulfilled') {
              pre.push(next.value.data)
            } else {
              pre.push('')
            }
            return pre
          }, images)
          ++imagePage
          // 判断是否超出请求数
          if (imagePage * reqNum < reqConfigData.length) {
            tempTime = setTimeout(() => {
              reqImg()
            }, 1000)
          } else {
            imagePage = 0
            resolve(images)
            clearTimeout(tempTime)
          }
        })
        .catch((res) => reject(res))
    }
    reqImg()
  })
}
